#pragma once

#ifndef __CR_CORE_FILE_SYSTEM_OPERATIONS_H__
#define __CR_CORE_FILE_SYSTEM_OPERATIONS_H__

NS_CR_BEGIN

namespace filesystem
{
	//--------------------------------------------------------------------------------------//
	//                                                                                      //
	//                            class filesystem_error                                    //
	//                                                                                      //
	//--------------------------------------------------------------------------------------//

	class filesystem_error
		: _STD system_error
	{
	public:
		filesystem_error(const std::string& _Message, std::error_code _Errcode = std::error_code())
			: _STD system_error(_Errcode, _Message)
		{}

		filesystem_error(const std::string& _Message, const path& _Path, std::error_code _Errcode)
			: _STD system_error(_Errcode, _Message)
			, _Mypath1(_Path)
		{}

		filesystem_error(const std::string& _Message, const path& _Path1, const path& _Path2, std::error_code _Errcode)
			: _STD system_error(_Errcode, _Message)
			, _Mypath1(_Path1)
			, _Mypath2(_Path2)
		{}

		virtual ~filesystem_error() NOEXCEPT
		{}

		filesystem_error(filesystem_error&& _Right)
			: system_error(_Right.code(), _Right.what())
			, _Mypath1(_STD move(_Right._Mypath1))
			, _Mypath2(_STD move(_Right._Mypath2))
		{}

		filesystem_error& operator=(filesystem_error&& _Right)
		{
			*(system_error*)this = *(system_error*)&_Right;
			_Mypath1 = _STD move(_Right._Mypath1);
			_Mypath2 = _STD move(_Right._Mypath2);
		}

		const path& path1() const NOEXCEPT
		{
			return (_Mypath1);
		}

		const path& path2() const NOEXCEPT
		{
			return (_Mypath2);
		}
	private:
		path _Mypath1;
		path _Mypath2;
	};

	//--------------------------------------------------------------------------------------//
	//                                     file_type                                        //
	//--------------------------------------------------------------------------------------//

	enum file_type
	{
		status_error,

		file_not_found,
		regular_file,
		directory_file,

		symlink_file,
		block_file,
		character_file,
		fifo_file,
		socket_file,
		reparse_file,  // Windows: FILE_ATTRIBUTE_REPARSE_POINT that is not a symlink
		type_unknown,  // file does exist, but isn't one of the above types or

		_detail_directory_symlink
	};

	//--------------------------------------------------------------------------------------//
	//                                       perms                                          //
	//--------------------------------------------------------------------------------------//

	enum perms
	{
		no_perms = 0,       // file_not_found is no_perms rather than perms_not_known

		// POSIX equivalent macros given in comments.
		// Values are from POSIX and are given in octal per the POSIX standard.

		// permission bits

		owner_read = 0400,  // S_IRUSR, Read permission, owner
		owner_write = 0200, // S_IWUSR, Write permission, owner
		owner_exe = 0100,   // S_IXUSR, Execute/search permission, owner
		owner_all = 0700,   // S_IRWXU, Read, write, execute/search by owner

		group_read = 040,   // S_IRGRP, Read permission, group
		group_write = 020,  // S_IWGRP, Write permission, group
		group_exe = 010,    // S_IXGRP, Execute/search permission, group
		group_all = 070,    // S_IRWXG, Read, write, execute/search by group

		others_read = 04,   // S_IROTH, Read permission, others
		others_write = 02,  // S_IWOTH, Write permission, others
		others_exe = 01,    // S_IXOTH, Execute/search permission, others
		others_all = 07,    // S_IRWXO, Read, write, execute/search by others

		all_all = 0777,     // owner_all|group_all|others_all

		// other POSIX bits

		set_uid_on_exe = 04000, // S_ISUID, Set-user-ID on execution
		set_gid_on_exe = 02000, // S_ISGID, Set-group-ID on execution
		sticky_bit = 01000,		// S_ISVTX,
								// (POSIX XSI) On directories, restricted deletion flag 
								  // (V7) 'sticky bit': save swapped text even after use 
								// (SunOS) On non-directories: don't cache this file
								// (SVID-v4.2) On directories: restricted deletion flag
								// Also see http://en.wikipedia.org/wiki/Sticky_bit

		perms_mask = 07777,     // all_all|set_uid_on_exe|set_gid_on_exe|sticky_bit

		perms_not_known = 0xFFFF, // present when directory_entry cache not loaded

								// options for permissions() function

		add_perms = 0x1000,     // adds the given permission bits to the current bits
		remove_perms = 0x2000,  // removes the given permission bits from the current bits;
														// choose add_perms or remove_perms, not both; if neither add_perms
														// nor remove_perms is given, replace the current bits with
														// the given bits.

		symlink_perms = 0x4000  // on POSIX, don't resolve symlinks; implied on Windows
	};

	//--------------------------------------------------------------------------------------//
	//                                    file_status                                       //
	//--------------------------------------------------------------------------------------//

	class file_status
	{
	public:
		file_status() 
			: m_value(status_error)
			, m_perms(perms_not_known) 
		{}
		explicit file_status(file_type v, perms prms = perms_not_known)
			: m_value(v)
			, m_perms(prms) 
		{}

		file_type  type() const 
		{ 
			return m_value; 
		}
		perms      permissions() const 
		{ 
			return m_perms; 
		}

		void type(file_type v) 
		{ 
			m_value = v; 
		}
		void permissions(perms prms) 
		{
			m_perms = prms;
		}


		bool operator==(const file_status& rhs) const 
		{
			return type() == rhs.type() && permissions() == rhs.permissions();
		}
		bool operator!=(const file_status& rhs) const 
		{ 
			return !(*this == rhs); 
		}
	private:
		file_type   m_value;
		enum perms  m_perms;
	};

	INLINE bool type_present(file_status f) 
	{ 
		return f.type() != status_error; 
	}
	INLINE bool permissions_present(file_status f)
	{
		return f.permissions() != perms_not_known;
	}
	INLINE bool status_known(file_status f) 
	{ 
		return type_present(f) && permissions_present(f); 
	}
	INLINE bool exists(file_status f) {
		return f.type() != status_error
			&& f.type() != file_not_found;
	}
	INLINE bool is_regular_file(file_status f)
	{ 
		return f.type() == regular_file;
	}
	INLINE bool is_directory(file_status f)
	{ 
		return f.type() == directory_file; 
	}
	INLINE bool is_symlink(file_status f) 
	{ 
		return f.type() == symlink_file; 
	}
	INLINE bool is_other(file_status f) 
	{
		return exists(f) && !is_regular_file(f)
			&& !is_directory(f) && !is_symlink(f);
	}

	struct space_info
	{
		uint64_t capacity;
		uint64_t free;
		uint64_t available;
	};

	enum class copy_option
	{
		none = 0, 
		fail_if_exists = none, 
		overwrite_if_exists
	};
	//--------------------------------------------------------------------------------------//
	//                             implementation details                                   //
	//--------------------------------------------------------------------------------------//

	namespace detail
	{
		enum copy_option 
		{ 
			none = 0, 
			fail_if_exists = none, 
			overwrite_if_exists 
		};

		
		file_status status(const path&p, _STD error_code* ec = 0);

		file_status symlink_status(const path& p, _STD error_code* ec = 0);

		bool is_empty(const path& p, _STD error_code* ec = 0);

		path initial_path(_STD error_code* ec = 0);

		path canonical(const path& p, const path& base, _STD error_code* ec = 0);

		void copy(const path& from, const path& to, _STD error_code* ec = 0);

		void copy_directory(const path& from, const path& to, _STD error_code* ec = 0);

		void copy_file(const path& from, const path& to,  // See ticket #2925
			detail::copy_option option, _STD error_code* ec = 0);

		void copy_symlink(const path& existing_symlink, const path& new_symlink, _STD error_code* ec = 0);

		bool create_directories(const path& p, _STD error_code* ec = 0);

		bool create_directory(const path& p, _STD error_code* ec = 0);

		void create_directory_symlink(const path& to, const path& from,
			_STD error_code* ec = 0);

		void create_hard_link(const path& to, const path& from, _STD error_code* ec = 0);

		void create_symlink(const path& to, const path& from, _STD error_code* ec = 0);

		path current_path(_STD error_code* ec = 0);

		void current_path(const path& p, _STD error_code* ec = 0);

		bool equivalent(const path& p1, const path& p2, _STD error_code* ec = 0);

		uint64_t file_size(const path& p, _STD error_code* ec = 0);

		uint64_t hard_link_count(const path& p, _STD error_code* ec = 0);

		std::time_t last_write_time(const path& p, _STD error_code* ec = 0);

		void last_write_time(const path& p, const std::time_t new_time,
			_STD error_code* ec = 0);

		void permissions(const path& p, perms prms, _STD error_code* ec = 0);

		path read_symlink(const path& p, _STD error_code* ec = 0);

		// For standardization, if the committee doesn't like "remove", consider "eliminate"
		bool remove(const path& p, _STD error_code* ec = 0);

		uint64_t remove_all(const path& p, _STD error_code* ec = 0);

		void rename(const path& old_p, const path& new_p, _STD error_code* ec = 0);

		void resize_file(const path& p, uintmax_t size, _STD error_code* ec = 0);

		space_info space(const path& p, _STD error_code* ec = 0);

		path system_complete(const path& p, _STD error_code* ec = 0);

		path temp_directory_path(_STD error_code* ec = 0);

		path unique_path(const path& p, _STD error_code* ec = 0);
	}

	//--------------------------------------------------------------------------------------//
	//                             status query functions                                   //
	//--------------------------------------------------------------------------------------//

	INLINE file_status status(const path& p) { return detail::status(p); }
	INLINE file_status status(const path& p, _STD error_code& ec)
	{
		return detail::status(p, &ec);
	}
	INLINE file_status symlink_status(const path& p) { return detail::symlink_status(p); }
	INLINE file_status symlink_status(const path& p, _STD error_code& ec)
	{
		return detail::symlink_status(p, &ec);
	}
	INLINE bool exists(const path& p) { return exists(detail::status(p)); }
	INLINE bool exists(const path& p, _STD error_code& ec)
	{
		return exists(detail::status(p, &ec));
	}
	INLINE bool is_directory(const path& p) { return is_directory(detail::status(p)); }
	INLINE bool is_directory(const path& p, _STD error_code& ec)
	{
		return is_directory(detail::status(p, &ec));
	}
	INLINE bool is_regular_file(const path& p) { return is_regular_file(detail::status(p)); }
	INLINE bool is_regular_file(const path& p, _STD error_code& ec)
	{
		return is_regular_file(detail::status(p, &ec));
	}
	INLINE bool is_other(const path& p) { return is_other(detail::status(p)); }
	INLINE bool is_other(const path& p, _STD error_code& ec)
	{
		return is_other(detail::status(p, &ec));
	}
	INLINE bool is_symlink(const path& p) { return is_symlink(detail::symlink_status(p)); }
	INLINE bool is_symlink(const path& p, _STD error_code& ec)
	{
		return is_symlink(detail::symlink_status(p, &ec));
	}


	INLINE bool is_empty(const path& p) { return detail::is_empty(p); }
	INLINE bool is_empty(const path& p, _STD error_code& ec)
	{
		return detail::is_empty(p, &ec);
	}

	//--------------------------------------------------------------------------------------//
	//                                                                                      //
	//                             operational functions                                    //
	//                  in alphabetical order, unless otherwise noted                       //
	//                                                                                      //
	//--------------------------------------------------------------------------------------//

	path current_path(); 
	path initial_path();

	path absolute(const path& p, const path& base = current_path());
	INLINE path canonical(const path& p, const path& base = current_path())
	{
		return detail::canonical(p, base);
	}
	INLINE path canonical(const path& p, _STD error_code& ec)
	{
		return detail::canonical(p, current_path(), &ec);
	}
	INLINE path canonical(const path& p, const path& base, _STD error_code& ec)
	{
		return detail::canonical(p, base, &ec);
	}

	INLINE void copy(const path& from, const path& to) { detail::copy(from, to); }

	INLINE void copy(const path& from, const path& to, _STD error_code& ec)
	{
		detail::copy(from, to, &ec);
	}
	INLINE void copy_directory(const path& from, const path& to)
	{
		detail::copy_directory(from, to);
	}
	INLINE void copy_directory(const path& from, const path& to, _STD error_code& ec)
	{
		detail::copy_directory(from, to, &ec);
	}
	INLINE void copy_file(const path& from, const path& to,   // See ticket #2925
		copy_option option)
	{
		detail::copy_file(from, to, static_cast<detail::copy_option>(option));
	}
	INLINE void copy_file(const path& from, const path& to)
	{
		detail::copy_file(from, to, detail::fail_if_exists);
	}
	INLINE void copy_file(const path& from, const path& to,   // See ticket #2925
			copy_option option, _STD error_code& ec)
	{
		detail::copy_file(from, to, static_cast<detail::copy_option>(option), &ec);
	}
	INLINE void copy_file(const path& from, const path& to, _STD error_code& ec)
	{
		detail::copy_file(from, to, detail::fail_if_exists, &ec);
	}
	INLINE void copy_symlink(const path& existing_symlink,
			const path& new_symlink) {
		detail::copy_symlink(existing_symlink, new_symlink);
	}

	INLINE void copy_symlink(const path& existing_symlink, const path& new_symlink, _STD error_code& ec)
	{
		detail::copy_symlink(existing_symlink, new_symlink, &ec);
	}
	INLINE bool create_directories(const path& p) { return detail::create_directories(p); }

	INLINE bool create_directories(const path& p, _STD error_code& ec)
	{
		return detail::create_directories(p, &ec);
	}
	INLINE bool create_directory(const path& p) { return detail::create_directory(p); }

	INLINE bool create_directory(const path& p, _STD error_code& ec)
	{
		return detail::create_directory(p, &ec);
	}
	INLINE void create_directory_symlink(const path& to, const path& from)
	{
		detail::create_directory_symlink(to, from);
	}
	INLINE void create_directory_symlink(const path& to, const path& from, _STD error_code& ec)
	{
		detail::create_directory_symlink(to, from, &ec);
	}
	INLINE void create_hard_link(const path& to, const path& new_hard_link) { detail::create_hard_link(to, new_hard_link); }

	INLINE void create_hard_link(const path& to, const path& new_hard_link, _STD error_code& ec)
	{
		detail::create_hard_link(to, new_hard_link, &ec);
	}
	INLINE void create_symlink(const path& to, const path& new_symlink) { detail::create_symlink(to, new_symlink); }

	INLINE void create_symlink(const path& to, const path& new_symlink, _STD error_code& ec)
	{
		detail::create_symlink(to, new_symlink, &ec);
	}
	INLINE path current_path() { return detail::current_path(); }

	INLINE path current_path(_STD error_code& ec) { return detail::current_path(&ec); }

	INLINE void current_path(const path& p) { detail::current_path(p); }

	INLINE void current_path(const path& p, _STD error_code& ec) { detail::current_path(p, &ec); }

	INLINE bool equivalent(const path& p1, const path& p2) { return detail::equivalent(p1, p2); }

	INLINE bool equivalent(const path& p1, const path& p2, _STD error_code& ec)
	{
		return detail::equivalent(p1, p2, &ec);
	}
	INLINE uint64_t file_size(const path& p) { return detail::file_size(p); }

	INLINE uint64_t file_size(const path& p, _STD error_code& ec)
	{
		return detail::file_size(p, &ec);
	}
	INLINE uint64_t hard_link_count(const path& p) { return detail::hard_link_count(p); }

	INLINE uint64_t hard_link_count(const path& p, _STD error_code& ec)
	{
		return detail::hard_link_count(p, &ec);
	}
	INLINE path initial_path() { return detail::initial_path(); }

	INLINE path initial_path(_STD error_code& ec) { return detail::initial_path(&ec); }

	template <class Path>
	path initial_path() { return initial_path(); }
	template <class Path>
	path initial_path(_STD error_code& ec) { return detail::initial_path(&ec); }

	INLINE std::time_t last_write_time(const path& p) { return detail::last_write_time(p); }

	INLINE std::time_t last_write_time(const path& p, _STD error_code& ec)
	{
		return detail::last_write_time(p, &ec);
	}
	INLINE void last_write_time(const path& p, const std::time_t new_time)
	{
		detail::last_write_time(p, new_time);
	}
	INLINE void last_write_time(const path& p, const std::time_t new_time, _STD error_code& ec)
	{
		detail::last_write_time(p, new_time, &ec);
	}
	INLINE void permissions(const path& p, perms prms)
	{
		detail::permissions(p, prms);
	}
	INLINE void permissions(const path& p, perms prms, _STD error_code& ec)
	{
		detail::permissions(p, prms, &ec);
	}

	INLINE path read_symlink(const path& p) { return detail::read_symlink(p); }

	INLINE path read_symlink(const path& p, _STD error_code& ec)
	{
		return detail::read_symlink(p, &ec);
	}
	
	// For standardization, if the committee doesn't like "remove", consider "eliminate"
	INLINE	bool remove(const path& p) { return detail::remove(p); }

	INLINE bool remove(const path& p, _STD error_code& ec) { return detail::remove(p, &ec); }

	INLINE uint64_t remove_all(const path& p) { return detail::remove_all(p); }

	INLINE uint64_t remove_all(const path& p, _STD error_code& ec)
	{
		return detail::remove_all(p, &ec);
	}
	INLINE void rename(const path& old_p, const path& new_p) { detail::rename(old_p, new_p); }

	INLINE void rename(const path& old_p, const path& new_p, _STD error_code& ec)
	{
		detail::rename(old_p, new_p, &ec);
	}
	// name suggested by Scott McMurray
	INLINE void resize_file(const path& p, uintmax_t size) { detail::resize_file(p, size); }

	INLINE void resize_file(const path& p, uintmax_t size, _STD error_code& ec)
	{
		detail::resize_file(p, size, &ec);
	}
	INLINE space_info space(const path& p) { return detail::space(p); }

	INLINE space_info space(const path& p, _STD error_code& ec) { return detail::space(p, &ec); }


	INLINE path system_complete(const path& p) { return detail::system_complete(p); }

	INLINE path system_complete(const path& p, _STD error_code& ec)
	{
		return detail::system_complete(p, &ec);
	}
	INLINE path temp_directory_path() { return detail::temp_directory_path(); }

	INLINE path temp_directory_path(_STD error_code& ec)
	{
		return detail::temp_directory_path(&ec);
	}
	INLINE path unique_path(const path& p = "%%%%-%%%%-%%%%-%%%%")
	{
		return detail::unique_path(p);
	}
	INLINE path unique_path(const path& p, _STD error_code& ec)
	{
		return detail::unique_path(p, &ec);
	}
}

NS_CR_END

#endif // !__CR_CORE_FILE_SYSTEM_OPERATIONS_H__
